package com.wan.data.mysql;

import cn.hutool.db.Db;
import cn.hutool.db.ds.DataSourceWrapper;
import cn.hutool.db.handler.RsHandler;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import com.wan.core.shutdown.ShutdownManager;
import com.wan.data.mysql.exception.DBInitializeException;
import com.wan.data.mysql.factory.CustomHikariCpFactory;
import com.wan.data.mysql.factory.DataSourceFactory;
import com.wan.data.mysql.format.DbFormat;
import com.wan.data.mysql.orm.annotion.Index;
import com.wan.data.mysql.orm.meta.ColumnMeta;
import com.wan.data.mysql.orm.meta.IndexMeta;
import com.wan.data.mysql.orm.meta.TableMeta;
import com.wan.data.mysql.sql.DDLSql;
import com.wan.data.mysql.util.AlterType;
import com.wan.data.mysql.util.ColumnType;
import com.wan.setting.Debug;
import com.wan.core.strategy.StrategyPool;

import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import java.util.stream.Collectors;

public class DataSourceManager {

    private static final Log log = LogFactory.get();
    private static final Map<String,DataSource> dataSourceMap = new HashMap<>();
    private static final Map<String,Db> dbMap = new HashMap<>();
    private static DataSourceFactory dataSourceFactory;
    /** 是否自动创建库和表 */
    private static boolean autoCreate = false;
    /** 是否自动修复表结构 */
    private static boolean autoFix = false;
    static {
        ShutdownManager.dependOn(DataSourceManager::shutdown,"logger");
    }

    public static void load(String...dbPaths) throws Exception {
        DbFormat strategy = StrategyPool.getStrategy(DbFormat.class);
        if(dataSourceFactory == null){
            dataSourceFactory = new CustomHikariCpFactory();
        }
        //初始化多数据源
        for (String path : dbPaths) {
            DataSourceFactory.JdbcSetting jdbcSetting = dataSourceFactory.resolverSetting(path);
            jdbcSetting.dbName = strategy.formatDbName(jdbcSetting.dbName);
            if(dataSourceMap.containsKey(jdbcSetting.dbName)) {
                continue;
            }
            //获取mysql基本库去检测实际连接的数据库
            DataSource helpDataSource = dataSourceMap.computeIfAbsent(DbConstant.DB_HELP, d -> {
                DataSourceFactory.JdbcSetting helpSetting = new DataSourceFactory.JdbcSetting(jdbcSetting);
                helpSetting.dbName = d;
                helpSetting.reconnectUrlStr();
                helpSetting.setting.put("minimumIdle","1"); //help初始和最小只给1个连接
                return dataSourceFactory.createDataSource(helpSetting);
            });
            //检测创库
            checkDatabase(helpDataSource,jdbcSetting.dbName);
            jdbcSetting.reconnectUrlStr();
            DataSourceWrapper dataSource = dataSourceFactory.createDataSource(jdbcSetting);
            dataSourceMap.put(jdbcSetting.dbName,dataSource);
            dbMap.put(jdbcSetting.dbName,Db.use(dataSource));
            log.info("初始化mysql连接池 -> {}",jdbcSetting.prefix + jdbcSetting.dbName);
        }
    }

    public static DataSource getDataSource(String key){
        return dataSourceMap.get(key);
    }

    public static Db getDb(String key, Supplier<RuntimeException> exceptionSupplier){
        Db db = dbMap.get(key);
        if(db == null){
            throw exceptionSupplier.get();
        }
        return db;
    }

    public static Db getDbThrow(String key){
        return getDb(key,()->new DBInitializeException("数据库连接不存在 -> " + key));
    }

    /**
     * 数据库自动创建
     */
    public static void checkDatabase(DataSource helpDataSource,String dbName) throws Exception{
        if(!autoCreate){
            return;
        }
        Db db = dbMap.computeIfAbsent(DbConstant.DB_HELP, name -> Db.use(helpDataSource));
        db.execute(DDLSql.createDbSql(dbName));
    }

    /**
     * 数据表自动创建
     */
    public static void createTableIfNotExist(TableMeta tableMeta) throws Exception {
        if(!autoCreate){
            return;
        }
        Db db = getDbThrow(tableMeta.getDatabaseName());
        String createSql = DDLSql.createTableSql(tableMeta);
        //log.info("创建表[{}],SQL:\n{}",tableMeta.getTableName(),createSql);
        int execute = db.execute(createSql);
        if(execute > 0){
            log.info("---创建表{}成功------",tableMeta.getTableName());
        }
    }

    /**
     * 修复表结构
     * 删除字段和字段长度降低需要debug模式开启
     * 字段名和表自增起始值不允许修改
     */
    public static void fixTable(TableMeta tableMeta) throws Exception{
        if(!autoFix){
            return;
        }
        Db db = getDbThrow(DbConstant.DB_HELP);
        //源数据表结构
        String queryColumnsInfoSql = DDLSql.queryColumnsInfoSql(tableMeta);
        Map<String, ColumnMeta> dbColumnMetas = db.query(queryColumnsInfoSql, (RsHandler<Map<String, ColumnMeta>>) resultSet -> {
            Map<String,ColumnMeta> columnMetaMap = new HashMap<>();
            while (resultSet.next()){
                String columnName = resultSet.getString("COLUMN_NAME");
                boolean isNotNull = resultSet.getString("IS_NULLABLE").equals("NO");
                String columnType = resultSet.getString("COLUMN_TYPE");
                String extra = resultSet.getString("EXTRA");
                boolean autoIncrement = extra != null && extra.contains("auto_increment");
                String columnComment = resultSet.getString("COLUMN_COMMENT");
                String columnKey = resultSet.getString("COLUMN_KEY");
                ColumnMeta columnMeta = new ColumnMeta(columnName,isNotNull
                        ,columnComment,columnType,autoIncrement);
                if(columnKey != null && columnKey.contains("PRI")){
                    columnMeta.setPk();
                }
                columnMetaMap.put(columnName,columnMeta);
            }
            return columnMetaMap;
        });
        //实体结构
        Map<String, ColumnMeta> entityMetas = tableMeta.getColumnMetas().stream()
                .collect(Collectors.toMap(ColumnMeta::getColumnName, c -> c));

        Map<AlterType,List<ColumnMeta>> columnAlters = new HashMap<>();
        //新增字段
        columnAlters.put(AlterType.ADD,entityMetas.values().stream()
                .filter(m-> !dbColumnMetas.containsKey(m.getColumnName())).collect(Collectors.toList()));
        if(Debug.isDebug()){
            //删除字段
            columnAlters.put(AlterType.DROP,dbColumnMetas.values().stream()
                    .filter(m-> !entityMetas.containsKey(m.getColumnName())).collect(Collectors.toList()));
        }
        String newPrimary = null;
        //修改字段
        List<ColumnMeta> modifyColumns = new ArrayList<>();
        columnAlters.put(AlterType.MODIFY,modifyColumns);
        for (ColumnMeta columnMeta : entityMetas.values()) {
            ColumnMeta dbColumnMeta = dbColumnMetas.get(columnMeta.getColumnName());
            if(dbColumnMeta == null){
                continue;
            }
            //主键变更
            if(columnMeta.isPk() && !dbColumnMeta.isPk()){
                newPrimary = columnMeta.getColumnName();
            }
            //类型和长度变更
            ColumnType columnType = ColumnType.parse(columnMeta.getColumnType());
            ColumnType dbColumnType = ColumnType.parse(dbColumnMeta.getColumnType());
            if(columnType.getLength() < dbColumnType.getLength() && !Debug.isDebug()){
                throw new RuntimeException("非debug模式不允许减少字段长度,table:" + tableMeta.getTableName() + ",column:" + columnMeta.getColumnName());
            }
            if(!columnType.equals(dbColumnType)){
                modifyColumns.add(columnMeta);
            }
            //可为空、自增、描述
            if(columnMeta.isNotNull() != dbColumnMeta.isNotNull() || columnMeta.isAutoIncrement() != dbColumnMeta.isAutoIncrement()
                    || !columnMeta.getComment().equals(dbColumnMeta.getComment())){
                modifyColumns.add(columnMeta);
            }
        }
        //索引结构
        Map<String, IndexMeta> indexMetas = getIndexInfo(tableMeta);
        Map<AlterType,List<IndexMeta>> indexAlters = new HashMap<>();
        for (IndexMeta indexMeta : tableMeta.getIndexColumnMetas()) {
            IndexMeta dbIndexMeta = indexMetas.remove(indexMeta.getName());
            //新增
            if(dbIndexMeta == null){
                indexAlters.computeIfAbsent(AlterType.ADD,t->new ArrayList<>()).add(indexMeta);
                continue;
            }
            List<ColumnMeta> columnMetas = indexMeta.getColumnMetas();
            List<String> dbIndexColumns = dbIndexMeta.getColumns();
            //变更
            if(!indexMeta.getType().equals(dbIndexMeta.getType()) || columnMetas.size() != dbIndexColumns.size()){
                indexAlters.computeIfAbsent(AlterType.MODIFY,t->new ArrayList<>()).add(indexMeta);
                continue;
            }
            for (ColumnMeta columnMeta : columnMetas) {
                if(dbIndexColumns.stream().noneMatch(d->d.equals(columnMeta.getColumnName()))){
                    indexAlters.computeIfAbsent(AlterType.MODIFY,t->new ArrayList<>()).add(indexMeta);
                    break;
                }
            }
        }
        //删除
        if(!indexMetas.isEmpty()){
            indexAlters.computeIfAbsent(AlterType.DROP,t->new ArrayList<>()).addAll(indexMetas.values());
        }
        String alterSQL = DDLSql.alterTableSql(tableMeta, columnAlters, indexAlters,newPrimary);
        if(alterSQL == null){
            return;
        }
        //执行修复
        log.info("修复{}表结构,debug[{}],SQL: \n{}",tableMeta.getTableName(),Debug.isDebug(),alterSQL);
        Db currTableDb = getDbThrow(tableMeta.getDatabaseName());
        currTableDb.execute(alterSQL);
    }

    /**
     * 获取索引信息
     */
    private static Map<String, IndexMeta> getIndexInfo(TableMeta tableMeta) throws Exception{
        Db db = getDbThrow(tableMeta.getDatabaseName());
        String showIndexSql = DDLSql.showIndex(tableMeta);
        return db.query(showIndexSql, (RsHandler<Map<String, IndexMeta>>) resultSet -> {
            Map<String, IndexMeta> result = new HashMap<>();
            while (resultSet.next()) {
                String columnName = resultSet.getString("Column_name");
                String indexName = resultSet.getString("Key_name");
                int nonUnique = resultSet.getInt("Non_unique");
                //主键索引忽略
                if(indexName.equals("PRIMARY")){
                    continue;
                }
                String type = nonUnique == 0 ? Index.INDEX_UNIQUE : Index.INDEX_NORMAL;
                IndexMeta indexMeta = result.computeIfAbsent(indexName, name -> {
                    IndexMeta meta = new IndexMeta(name, type);
                    meta.setColumns(new ArrayList<>());
                    return meta;
                });
                indexMeta.getColumns().add(columnName);
            }
            return result;
        });
    }

    public static void setAutoCreate(boolean autoCreate){
        DataSourceManager.autoCreate = autoCreate;
    }

    public static void setAutoFix(boolean autoFix){
        DataSourceManager.autoFix = autoFix;
    }

    public static void setDataSourceFactory(DataSourceFactory dataSourceFactory){
        DataSourceManager.dataSourceFactory = dataSourceFactory;
    }

    public static void shutdown(){
        dataSourceMap.values().forEach(dataSource -> {
            if(dataSource instanceof AutoCloseable){
                AutoCloseable autoCloseable = (AutoCloseable) dataSource;
                try {
                    autoCloseable.close();
                } catch (Exception exception) {
                    log.error(exception);
                }
            }
        });
        dbMap.clear();
    }
}
